package fr.inserm.um915.bdibd.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;


import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

/**
 * 
 * CmdTool
 *
 */
public abstract class CmdTool
	{
	private static final Logger LOG=Logger.getLogger("fr.inserm.um915.bdibd");

	/** associated model */
	protected Model model=new Model();
	/** BerkeleyDB home */
	protected File dbHome=null;
	/** Transaction */
	protected Transaction txn=null;
	/** flag : selected pairs */
	protected List<Boolean> selectedPairIds=new ArrayList<Boolean>();
	/** limit ibd 0 */
	protected float ibd0Limit=0.1f;
	/** output stream */
	protected PrintStream out=System.out;
	

	static private class NamedPair
		{
		String fam1;
		String indi1;
		String fam2;
		String indi2;
		@Override
		public String toString() {
			return fam1+":"+indi1+"/"+fam2+":"+indi2;
			}
		}
	
	
	protected static class FindPeaks extends CmdTool
		{
		static class Peak implements Comparable<Peak>
			{
			String marker_start=null;
			String marker_end=null;
			
			ChromStartEnd segment=null;
			int count=0;
			int n_markers=0;
			@Override
			public int compareTo(Peak p)
				{
				return segment.compareTo(p.segment);
				}
			}
		
		FindPeaks()
			{
			
			}
		
		@Override
		protected void usage(String args[])
			{
			super.usage(args);
			System.out.println("  --restrict-pair fam1 indi1 fam2 indi2");
			System.out.println("  --restrict-pairs (file)");
			}
		
		@Override
		protected void run(String args[]) throws Exception
			{
			List<NamedPair> restrictPairs=new ArrayList<NamedPair>();

			int optind=1;
			int id2=-1;
			
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					usage(args);
					return ;
					}
				else if((id2=parseArguments(optind,args))!=-1)
					{
					optind=id2;
					}
				else if(args[optind].equals("--restrict-pair") &&  optind+4< args.length)
					{
					NamedPair p=new NamedPair();
					p.fam1=(args[++optind]);
					p.indi1=(args[++optind]);
					p.fam2=(args[++optind]);
					p.indi2=(args[++optind]);
					restrictPairs.add(p);
					}
				else if(args[optind].equals("--restrict-pairs") &&  optind+1< args.length)
					{
					BufferedReader r=new BufferedReader(new FileReader(args[++optind]));
					String line;
					while((line=r.readLine())!=null)
						{
						if(line.startsWith("#")) continue;
						line=line.trim();
						if(line.isEmpty()) continue;
						String tokens[]=line.split("\t");
						if(tokens.length<4) throw new IOException("expected 4 tokens in "+line);
						NamedPair p=new NamedPair();
						p.fam1=(tokens[0]);
						p.indi1=(tokens[1]);
						p.fam2=(tokens[2]);
						p.indi2=(tokens[3]);
						restrictPairs.add(p);
						}
					r.close();
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					this.usage(args);
					System.err.println("Unknown option "+args[optind]);
					}
				else
					{
					break;
					}
				++optind;
				}
			
			if(optind!=args.length)
				{
				System.err.println("Illegal number of arguments");
				return;
				}
			
			open();
			while(this.selectedPairIds.size()< this.model.getPairCount())
				{
				this.selectedPairIds.add(restrictPairs.isEmpty());
				}
		
		for(NamedPair p:restrictPairs)
			{
			Individual i1= model.getIndividualbyName(txn, p.fam1, p.indi1);
			if(i1==null)
				{
				System.err.println("Cannot 1st individual of "+p);
				close();
				return;
				}
			Individual i2= model.getIndividualbyName(txn, p.fam2, p.indi2);
			if(i2==null)
				{
				System.err.println("Cannot 2nd individual of "+p);
				close();
				return;
				}
			int pair_id=model.findPairIdByIndividuals(txn, i1, i2);
			if(pair_id==-1)
				{
				System.err.println("Cannot get pair_id for "+p);
				close();
				return;
				}
			
			this.selectedPairIds.set(pair_id,true);
			}
		
		Cursor c=null;
		
		List<Peak> peaks=new ArrayList<Peak>(500000);
		try
			{
			c= this.model.getPosToMarkerDatabase().openCursor(txn, null);
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			int index_marker=0;
			for(;;)
				{
				OperationStatus status=c.getNext(key, data, LockMode.DEFAULT);
				
				if(status!=OperationStatus.SUCCESS)
					{
					break;
					}
				
				
				Marker m=Marker.BINDING.entryToObject(data);
				
				if(((++index_marker)%1000==0))
					{
					LOG.info(m.getName()+" n="+index_marker);
					}

				
				LinkageRow statuses=model.getIBDByMarker(txn, m.getName());
				if(statuses==null)
					{
					System.err.println("??No data for "+m);
					return;
					}
				Peak p=new Peak();
				p.marker_start=m.getName();
				p.marker_end=m.getName();
				p.n_markers=1;
				p.segment=new ChromStartEnd(
					m.getPosition().getChromosomeId(),
					m.getPosition().getPosition(),
					m.getPosition().getPosition()
					);
				for(int pair_id=0;pair_id< model.getPairCount();++pair_id)
					{
					if(!this.selectedPairIds.get(pair_id)) continue;
					float ibd0=statuses.getIbd0(pair_id);
					if(ibd0<this.ibd0Limit) continue;
					p.count++;
					}
				peaks.add(p);
				}
			Collections.sort(peaks);
			int i=1;
			while(i<peaks.size())
				{
				Peak curr=peaks.get(i);
				Peak prev=peaks.get(i-1);
				if(curr.segment.getChromosomeId() != prev.segment.getChromosomeId() ||
					curr.count!=prev.count	
					)
					{
					++i;
					continue;
					}
				prev.segment=new ChromStartEnd(
					prev.segment.getChromosomeId(),
					prev.segment.getStart(),
					curr.segment.getEnd()
					);
				prev.marker_end=curr.marker_end;
				prev.n_markers+=curr.n_markers;
				peaks.remove(i);
				}
			
			i=0;
			while(i<peaks.size())
				{
				Peak p=peaks.get(i);
				int start=p.segment.getStart();
				if(i==0 || peaks.get(i-1).segment.getChromosomeId()!=p.segment.getChromosomeId())
					{
					start=0;
					}
				else
					{
					start=peaks.get(i-1).segment.getStart()+1;
					}
				int end=p.segment.getEnd();
				if(i+1==peaks.size() || peaks.get(i+1).segment.getChromosomeId()!=p.segment.getChromosomeId())
					{
					end=250000000;//max length human chrom
					}
				else
					{
					end=peaks.get(i+1).segment.getEnd()-1;
					}
				out.print(p.segment.getChromosome());
				out.print('\t');
				out.print(p.marker_start);
				out.print('\t');
				out.print(start);
				out.print('\t');
				out.print(end);
				out.print('\t');
				out.print(p.marker_end);
				out.print('\t');
				out.print(p.count);
				out.print('\t');
				out.print(p.n_markers);
				out.println();
				i++;
				}
			
			}
		catch(Exception err)
			{
			throw err;
			}
		finally
			{	
			c.close();
			}
		
		
		}
	}

	
	
	protected static class DumpPairs extends CmdTool
		{
		
		@Override
		protected void usage(String args[])
			{
			super.usage(args);
			System.out.println("  --self (hide self/self)");
			System.out.println("  --parent (hide child/parent)");
			}

		
		@Override
		protected void run(String[] args) throws Exception
			{
			boolean show_self_self=true;
			boolean show_parent_child=true;
			int optind=1;
			int id2=-1;
			
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					usage(args);
					return ;
					}
				else if((id2=parseArguments(optind,args))!=-1)
					{
					optind=id2;
					}
				else if(args[optind].equals("--self"))
					{
					show_self_self=false;
					}
				else if(args[optind].equals("--parent"))
					{
					show_parent_child=false;
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					this.usage(args);
					System.err.println("Unknown option "+args[optind]);
					}
				else
					{
					break;
					}
				++optind;
				}
			
			if(optind!=args.length)
				{
				System.err.println("Illegal number of arguments");
				return;
				}
			
			open();
			for(int pair_id=0;pair_id<model.getPairCount();++pair_id)
				{
				PairOfIndividual p=model.getPairOfIndividualByPairId(txn, pair_id);
				if(!show_self_self && p.isSelfSelf())
					{
					continue;
					}
				if(!show_parent_child && p.isParentChild())
					{
					continue;
					}
				
				printIndividual(p.getFirst());
				out.print('\t');
				printIndividual(p.getSecond());
				out.print('\n');
				}
			}
		}
	protected static class DumpIndividals extends CmdTool
		{
		@Override
		protected void run(String[] args) throws Exception
			{
			int optind=1;
			int id2=-1;
			
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					usage(args);
					return ;
					}
				else if((id2=parseArguments(optind,args))!=-1)
					{
					optind=id2;
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					this.usage(args);
					System.err.println("Unknown option "+args[optind]);
					}
				else
					{
					break;
					}
				++optind;
				}
			
			if(optind!=args.length)
				{
				System.err.println("Illegal number of arguments");
				return;
				}
			
			open();
			for(int indi_id=0;indi_id<model.getIndividualCount();++indi_id)
				{
				Individual i=model.getIndividualByIndex(txn, indi_id);
				printIndividual(i);
				out.print('\n');
				}
			}
		}
	
	
	protected static class DumpMarkers extends CmdTool
		{
		@Override
		protected void run(String[] args) throws Exception
			{
			int optind=1;
			int id2=-1;
			
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					usage(args);
					return ;
					}
				else if((id2=parseArguments(optind,args))!=-1)
					{
					optind=id2;
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					this.usage(args);
					System.err.println("Unknown option "+args[optind]);
					}
				else
					{
					break;
					}
				++optind;
				}
			
			if(optind!=args.length)
				{
				System.err.println("Illegal number of arguments");
				return;
				}
			
			open();
			Cursor c=null;
			
			try
				{
				c= this.model.getPosToMarkerDatabase().openCursor(txn, null);
				DatabaseEntry key=new DatabaseEntry();
				DatabaseEntry data=new DatabaseEntry();
				for(;;)
					{
					OperationStatus status=c.getNext(key, data, LockMode.DEFAULT);
					
					if(status!=OperationStatus.SUCCESS)
						{
						break;
						}
					
					
					Marker m=Marker.BINDING.entryToObject(data);
					
					this.out.print(m.getPosition().getChromosome());
					this.out.print('\t');
					this.out.print(m.getPosition().getPosition());
					this.out.print('\t');
					this.out.print(m.getName());
					this.out.print('\n');
					}
				
				}
			catch(Exception err)
				{
				throw err;
				}
			finally
				{	
				c.close();
				}
			}
		}

	
	protected static class DumpMeta extends CmdTool
		{
		@Override
		protected void run(String[] args) throws Exception
			{
			int optind=1;
			int id2=-1;
			
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					usage(args);
					return ;
					}
				else if((id2=parseArguments(optind,args))!=-1)
					{
					optind=id2;
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					this.usage(args);
					System.err.println("Unknown option "+args[optind]);
					}
				else
					{
					break;
					}
				++optind;
				}
			
			if(optind!=args.length)
				{
				System.err.println("Illegal number of arguments");
				return;
				}
			
			open();
			Map<String,String> m=model.getMeta(txn);
			for(String k:m.keySet())
				{
				out.print(k);
				out.print('\t');
				out.print(m.get(k));
				out.println();
				}
			}
		}
	
	protected CmdTool()
		{
		}
	
	
	protected abstract void run(String args[]) throws Exception;
	
	@Override
	protected void finalize() throws Throwable
		{
		close();
		super.finalize();
		}
	
	protected void printIndividual(Individual indi)
		{
		out.print(indi.getFamily());
		out.print('\t');
		out.print(indi.getName());
		out.print('\t');
		out.print(indi.getFatherId());
		out.print('\t');
		out.print(indi.getMotherId());
		out.print('\t');
		out.print(indi.getGender());
		out.print('\t');
		out.print(indi.getStatus());
		}

	public void open() throws IOException
		{
		if(this.dbHome==null)
			{
			throw new NullPointerException("db-home was not defined");
			}
		close();
		this.model.open(dbHome, true);
		}
	
	public void close()
		{
		this.model.close();
		}
	
	protected void usage(String args[])
		{
		System.out.println("  -d (directory) bdb-home");
		}
	
	int parseArguments(int optind,String args[])
		{
		if(args[optind].equals("-d") &&  optind+1< args.length)
			{
			this.dbHome=new File(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-L") &&  optind+1< args.length)
			{
			this.ibd0Limit=Float.parseFloat(args[++optind]);
			return optind;
			}
		return -1;
		}
	

	
	private static void main_usage(String args[])
		{
		System.err.println("Usage: (program) [args].");
		System.err.println("Programs:");
		System.err.println(" * findpeak");
		System.err.println(" * pairs");
		System.err.println(" * individuals");
		System.err.println(" * markers");
		System.err.println(" * meta");
		}
	
	/**
	 * @param args
	 */
	public static void main(String[] args)
		{		
		CmdTool curr=null;
		if(args.length==0)
			{
			main_usage(args);
			return;
			}
		if(args[0].equals("findpeak"))
			{
			curr=new FindPeaks();
			}
		else if(args[0].equals("pairs"))
			{
			curr=new DumpPairs();
			}
		else if(args[0].equals("individuals"))
			{
			curr=new DumpIndividals();
			}
		else if(args[0].equals("markers"))
			{
			curr=new DumpMarkers();
			}
		else if(args[0].equals("meta"))
			{
			curr=new DumpMeta();
			}
		else
			{
			main_usage(args);
			System.err.println("Unknown program: "+args[0]);
			}
		
		final CmdTool app=curr;
		

		Runtime.getRuntime().addShutdownHook(new Thread()
			{
			@Override
			public void run() {
				app.close();
				}
			});
		
		
		
		
		try {
			app.run(args);
			}
		catch (Exception e) {
			e.printStackTrace();
			}
		finally
			{
			app.close();
			}
		}
}
